#!/bin/bash

# ======================================
# 通用Jar包启停脚本
# 支持：start|stop|restart|status|logs|info
# 示例：./app.sh start /path/to/your-app.jar -c config.properties -j "-Xmx512m"
# ======================================

# 默认配置
DEFAULT_JVM_OPTS="-Xms256m -Xmx512m -XX:MetaspaceSize=128m"
LOG_DIR="./logs"
APP_PID=""
CONFIG_FILE=""
SCRIPT_DIR=$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)

# 颜色输出
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[0;33m'
BLUE='\033[0;34m'
MAGENTA='\033[0;35m'
CYAN='\033[0;36m'
NC='\033[0m' # 无颜色

# 帮助信息
show_help() {
    echo -e "${CYAN}用法: $0 [start|stop|restart|status|logs|info] <jar包路径> [选项]${NC}"
    echo "选项:"
    echo "  -c, --config <配置文件>    指定配置文件路径"
    echo "  -j, --jvm-opts <JVM参数>  指定JVM参数"
    echo "  -l, --log-dir <日志目录>   指定日志目录"
    echo "  -h, --help                 显示此帮助信息"
    exit 1
}

# 规范化路径
normalize_path() {
    local path="$1"
    local base_dir="${2:-$(pwd)}"
    
    # 如果路径是相对的，转换为相对于base_dir的绝对路径
    if [[ "$path" != /* ]]; then
        path="$base_dir/$path"
    fi
    
    # 解析路径中的符号链接和.、..
    local resolved_path=$(readlink -f "$path" 2>/dev/null)
    if [[ -n "$resolved_path" ]]; then
        echo "$resolved_path"
    else
        # 如果readlink不可用，使用cd和pwd组合
        local dir=$(dirname "$path")
        local file=$(basename "$path")
        (cd "$dir" 2>/dev/null && echo "$(pwd)/$file") || echo "$path"
    fi
}

# 解析命令行参数
parse_args() {
    ACTION="$1"
    JAR_PATH="$2"

    if [ -z "$ACTION" ] || [ -z "$JAR_PATH" ]; then
        show_help
    fi

    # 规范化JAR路径
    JAR_PATH=$(normalize_path "$JAR_PATH")

    if [ ! -f "$JAR_PATH" ]; then
        echo -e "${RED}错误: Jar包不存在: $JAR_PATH${NC}"
        exit 1
    fi

    # 获取Jar包名称（不含路径和后缀）
    APP_NAME=$(basename "$JAR_PATH" .jar)

    # 解析额外选项
    shift 2
    while [[ $# -gt 0 ]]; do
        case "$1" in
            -c|--config)
                CONFIG_FILE="$2"
                shift 2
                ;;
            -j|--jvm-opts)
                JVM_OPTS="$2"
                shift 2
                ;;
            -l|--log-dir)
                LOG_DIR="$2"
                shift 2
                ;;
            -h|--help)
                show_help
                ;;
            *)
                echo -e "${RED}未知选项: $1${NC}"
                show_help
                ;;
        esac
    done

    # 设置默认JVM参数
    if [ -z "$JVM_OPTS" ]; then
        JVM_OPTS="$DEFAULT_JVM_OPTS"
    fi

    # 规范化日志目录路径
    LOG_DIR=$(normalize_path "$LOG_DIR" "$(dirname "$JAR_PATH")")

    # 日志文件路径
    LOG_FILE="${LOG_DIR}/${APP_NAME}.log"
    PID_FILE="${LOG_DIR}/${APP_NAME}.pid"
    OUT_FILE="${LOG_DIR}/${APP_NAME}.out"

    # 创建日志目录
    mkdir -p "$LOG_DIR" || { echo -e "${RED}无法创建日志目录: $LOG_DIR${NC}"; exit 1; }
}

# 检查进程状态
check_status() {
    if [ -f "$PID_FILE" ]; then
        APP_PID=$(cat "$PID_FILE")
        
        # 检查进程是否存在
        if ps -p "$APP_PID" > /dev/null; then
            # 检查进程是否是我们要找的Java进程
            if grep -q "java" "/proc/$APP_PID/cmdline" 2>/dev/null; then
                return 0 # 进程存在且是Java进程
            else
                echo -e "${YELLOW}警告: PID文件存在，但进程不是Java进程，可能PID文件已过时${NC}"
            fi
        fi
        
        # 无效PID文件，删除
        echo -e "${YELLOW}清理过时的PID文件: $PID_FILE${NC}"
        rm -f "$PID_FILE"
    fi
    APP_PID=""
    return 1 # 进程不存在
}

# # 获取应用端口
# get_app_port() {
#     # 从JVM_OPTS中提取端口
#     if [[ "$JVM_OPTS" == *"server.port"* ]]; then
#         PORT=$(echo "$JVM_OPTS" | grep -oP 'server.port=\K\d+')
#         if [ -n "$PORT" ]; then
#             echo "$PORT"
#             return
#         fi
#     fi
    
#     # 从配置文件中提取端口
#     if [ -n "$CONFIG_FILE" ] && [ -f "$CONFIG_FILE" ]; then
#         if grep -q "server.port" "$CONFIG_FILE"; then
#             PORT=$(grep "server.port" "$CONFIG_FILE" | grep -oP 'server.port=\K\d+')
#             if [ -n "$PORT" ]; then
#                 echo "$PORT"
#                 return
#             fi
#         fi
#     fi
    
#     # 默认端口
#     echo "8080"
# }

# 获取应用端口（按配置加载顺序）
get_app_port() {
    local port=""
    local config_order=()  # 记录配置来源顺序
    
    # 1. 命令行参数（最高优先级）
    if [[ "$JVM_OPTS" || "$*" ]]; then
        port=$(get_port_from_cmdline)
        if [ -n "$port" ]; then
            config_order+=("命令行参数")
            echo -e "${GREEN}从命令行获取端口: $port${NC}"
            echo "$port"
            return 0
        fi
    fi
    
    # 2. 环境变量
    port=$(get_port_from_env)
    if [ -n "$port" ]; then
        config_order+=("环境变量")
        echo -e "${GREEN}从环境变量获取端口: $port${NC}"
        echo "$port"
        return 0
    fi
    
    # 3. 外部配置文件（-c指定的文件）
    if [ -n "$CONFIG_FILE" ] && [ -f "$CONFIG_FILE" ]; then
        port=$(get_port_from_external_config)
        if [ -n "$port" ]; then
            config_order+=("外部配置文件: $CONFIG_FILE")
            echo -e "${GREEN}从外部配置获取端口: $port${NC}"
            echo "$port"
            return 0
        fi
    fi
    
    # 4. JAR包内部配置文件
    port=$(get_port_from_jar_config)
    if [ -n "$port" ]; then
        config_order+=("JAR内部配置文件")
        echo -e "${GREEN}从JAR内部获取端口: $port${NC}"
        echo "$port"
        return 0
    fi
    
    # 5. 默认端口（最低优先级）
    config_order+=("默认端口")
    echo -e "${YELLOW}使用默认端口: 8080${NC}"
    echo "8080"
    
    # 输出配置加载顺序
    echo -e "${BLUE}配置加载顺序: ${config_order[*]}\n${NC}"
    return 0
}

# 从命令行参数获取端口
get_port_from_cmdline() {
    local cmdline="$JVM_OPTS $*"
    echo "$cmdline" | grep -oP '(?:--|-)server\.port=(\s*)\K\d+' | tr -d '[:space:]'
}

# 从环境变量获取端口
get_port_from_env() {
    echo "$SERVER_PORT" | grep -oP '^\d+$'
}

# 从外部配置文件获取端口
get_port_from_external_config() {
    local config_file=$(normalize_path "$CONFIG_FILE")
    local file_ext=$(echo "$config_file" | awk -F. '{print tolower($NF)}')
    
    case "$file_ext" in
        yml|yaml)
            grep -i "server\s*:\s*port\s*:" "$config_file" | grep -oP '\d+' | head -n1
            ;;
        properties|prop)
            grep -i "server\.port" "$config_file" | grep -oP 'server\.port=(\s*)\K\d+' | tr -d '[:space:]' | head -n1
            ;;
        *)
            grep -i "server\.port" "$config_file" | grep -oP '\d+' | head -n1
            ;;
    esac
}

# 从JAR包内部配置文件获取端口
get_port_from_jar_config() {
  local port=""

  if [ -f "$JAR_PATH" ]; then
      for config_file in "application.properties" "application.yml" "application.yaml"; do
          # 检查JAR包中是否存在该配置文件
          if jar tf "$JAR_PATH" | grep -qi "$config_file"; then
              # 临时提取配置文件
              jar xf "$JAR_PATH" "$config_file" 2>/dev/null

              if [ -f "$config_file" ]; then
                  # 使用专用函数提取端口，不包含任何日志输出
                  port=$(get_port_from_config_file "$config_file")

                  # 清理临时文件
                  rm -f "$config_file" 2>/dev/null

                  # 如果成功提取到端口，立即返回
                  if [ -n "$port" ]; then
                      echo "$port"
                      return 0
                  fi
              fi
          fi
      done
  fi

  return 1  # 未找到有效端口

}

# 通用配置文件端口提取函数
get_port_from_config_file() {
    local file="$1"
    local file_ext=$(echo "$file" | awk -F. '{print tolower($NF)}')
    
    case "$file_ext" in
        yml|yaml)
            grep -i "server\s*:\s*port\s*:" "$file" | grep -oP '\d+' | head -n1
            ;;
        properties|prop)
            grep -i "server\.port" "$file" | grep -oP 'server\.port=(\s*)\K\d+' | tr -d '[:space:]' | head -n1
            ;;
        *)
            grep -i "server\.port" "$file" | grep -oP '\d+' | head -n1
            ;;
    esac
}

# 启动应用
start_app() {
    check_status
    if [ $? -eq 0 ]; then
        echo -e "${YELLOW}应用已在运行中 (PID: $APP_PID)${NC}"
        return
    fi

    echo -e "${GREEN}正在启动 $APP_NAME...${NC}"
    echo -e "${BLUE}JAR路径: $JAR_PATH${NC}"
    echo -e "${BLUE}日志目录: $LOG_DIR${NC}"
    
    # 构建启动命令
    START_CMD="java $JVM_OPTS -jar $JAR_PATH"

    # 添加配置文件参数
    if [ -n "$CONFIG_FILE" ]; then
        if [ -f "$CONFIG_FILE" ]; then
            CONFIG_FILE=$(normalize_path "$CONFIG_FILE")
            START_CMD="$START_CMD --spring.config.location=$CONFIG_FILE"
            echo -e "${BLUE}配置文件: $CONFIG_FILE${NC}"
        else
            echo -e "${RED}警告: 配置文件不存在: $CONFIG_FILE${NC}"
        fi
    fi

    # 后台运行并记录PID
    echo -e "${BLUE}启动命令: $START_CMD${NC}"
    nohup $START_CMD > "$OUT_FILE" 2>&1 &
    echo $! > "$PID_FILE"

    echo -e "${GREEN}应用 $APP_NAME 已启动 (PID: $(cat $PID_FILE))${NC}"
    echo -e "${YELLOW}标准输出: $OUT_FILE${NC}"
    echo -e "${YELLOW}日志文件: $LOG_FILE${NC}"

    # 等待应用启动，检查端口是否监听
    PORT=$(get_app_port)
    echo -e "${YELLOW}正在检查端口 $PORT 是否监听...${NC}"
    
    for i in {1..30}; do
        if nc -z localhost "$PORT"; then
            echo -e "${GREEN}应用已就绪，端口 $PORT 正在监听${NC}"
            return
        fi
        
        # 检查应用是否崩溃
        if ! ps -p "$APP_PID" > /dev/null; then
            echo -e "${RED}错误: 应用启动失败，进程已退出${NC}"
            echo -e "${YELLOW}最近10行输出:${NC}"
            tail -n 10 "$OUT_FILE"
            return
        fi
        
        sleep 1
    done
    
    echo -e "${YELLOW}应用启动超时，但进程仍在运行。可能需要更长时间或应用未监听端口${NC}"
    echo -e "${YELLOW}检查应用状态: $0 status $JAR_PATH${NC}"
}

# 停止应用
stop_app() {
    check_status
    if [ $? -ne 0 ]; then
        echo -e "${YELLOW}应用未运行${NC}"
        return
    fi

    echo -e "${GREEN}正在停止 $APP_NAME (PID: $APP_PID)...${NC}"

    # 尝试优雅关闭
    kill "$APP_PID"

    # 等待进程退出
    for i in {1..15}; do
        check_status
        if [ $? -ne 0 ]; then
            echo -e "${GREEN}应用已停止${NC}"
            rm -f "$PID_FILE"
            return
        fi
        echo -e "${YELLOW}等待应用退出 ($i/15)...${NC}"
        sleep 1
    done

    # 强制终止
    echo -e "${RED}应用未响应，正在强制终止...${NC}"
    kill -9 "$APP_PID"

    # 再次检查
    sleep 1
    check_status
    if [ $? -ne 0 ]; then
        echo -e "${GREEN}应用已强制终止${NC}"
        rm -f "$PID_FILE"
    else
        echo -e "${RED}错误: 无法终止应用 (PID: $APP_PID)${NC}"
    fi
}

# 显示状态
show_status() {
    echo -e "${CYAN}=== $APP_NAME 状态信息 ===${NC}"
    check_status
    
    if [ $? -eq 0 ]; then
        echo -e "${GREEN}● 应用正在运行 (PID: $APP_PID)${NC}"
        
        # 显示内存使用情况
        if command -v ps >/dev/null 2>&1; then
            MEM_INFO=$(ps -o pid,%mem,rss,command -p "$APP_PID" | tail -n 1)
            echo -e "${BLUE}  内存使用: $MEM_INFO${NC}"
        fi
        
        PORT=$(get_app_port)
        echo -e "${BLUE}  监听端口: $PORT${NC}"
        echo -e "${BLUE}  PID 文件: $PID_FILE${NC}"
        echo -e "${BLUE}  日志文件: $LOG_FILE${NC}"
        echo -e "${BLUE}  标准输出: $OUT_FILE${NC}"
        echo -e "${BLUE}  JVM 参数: $JVM_OPTS${NC}"
        
        if [ -n "$CONFIG_FILE" ]; then
            echo -e "${BLUE}  配置文件: $CONFIG_FILE${NC}"
        fi
        
        # 显示启动时间
        if [ -f "$PID_FILE" ]; then
            START_TIME=$(stat -c %y "$PID_FILE" 2>/dev/null || date -r "$PID_FILE" 2>/dev/null)
            echo -e "${BLUE}  启动时间: $START_TIME${NC}"
        fi
    else
        echo -e "${RED}● 应用未运行${NC}"
        echo -e "${BLUE}  JAR 文件: $JAR_PATH${NC}"
        echo -e "${BLUE}  日志目录: $LOG_DIR${NC}"
    fi
    
    # 显示磁盘使用情况
    if [ -d "$LOG_DIR" ]; then
        DISK_USAGE=$(du -sh "$LOG_DIR" 2>/dev/null || echo "未知")
        echo -e "${BLUE}  日志目录大小: $DISK_USAGE${NC}"
    fi
}

# 显示日志
show_logs() {
    if [ ! -f "$LOG_FILE" ]; then
        echo -e "${RED}日志文件不存在: $LOG_FILE${NC}"
        
        # 检查输出文件
        if [ -f "$OUT_FILE" ]; then
            echo -e "${YELLOW}但输出文件存在: $OUT_FILE${NC}"
            echo -e "${YELLOW}显示输出文件内容...${NC}"
            tail -f "$OUT_FILE"
        else
            echo -e "${YELLOW}请先启动应用以生成日志${NC}"
        fi
        return
    fi

    echo -e "${GREEN}显示 $APP_NAME 的日志 (按 Ctrl+C 退出)${NC}"
    tail -f "$LOG_FILE"
}

# 显示详细信息
show_info() {
    echo -e "${CYAN}=== $APP_NAME 详细信息 ===${NC}"
    echo -e "${BLUE}JAR 文件: $JAR_PATH${NC}"
    echo -e "${BLUE}应用名称: $APP_NAME${NC}"
    echo -e "${BLUE}日志目录: $LOG_DIR${NC}"
    echo -e "${BLUE}PID 文件: $PID_FILE${NC}"
    echo -e "${BLUE}日志文件: $LOG_FILE${NC}"
    echo -e "${BLUE}标准输出: $OUT_FILE${NC}"
    echo -e "${BLUE}JVM 参数: $JVM_OPTS${NC}"
    
    if [ -n "$CONFIG_FILE" ]; then
        echo -e "${BLUE}配置文件: $CONFIG_FILE${NC}"
    fi
    
    PORT=$(get_app_port)
    echo -e "${BLUE}预期端口: $PORT${NC}"
    
    # 检查文件状态
    echo -e "${CYAN}=== 文件状态 ===${NC}"
    if [ -f "$JAR_PATH" ]; then
        JAR_SIZE=$(du -sh "$JAR_PATH" | cut -f1)
        JAR_MODIFIED=$(date -r "$JAR_PATH" "+%Y-%m-%d %H:%M:%S")
        echo -e "${GREEN}● JAR 文件存在 ($JAR_SIZE, 修改于 $JAR_MODIFIED)${NC}"
    else
        echo -e "${RED}● JAR 文件不存在${NC}"
    fi
    
    check_status
    if [ $? -eq 0 ]; then
        echo -e "${GREEN}● 应用正在运行 (PID: $APP_PID)${NC}"
    else
        echo -e "${RED}● 应用未运行${NC}"
    fi
    
    if [ -f "$PID_FILE" ]; then
        PID_MODIFIED=$(date -r "$PID_FILE" "+%Y-%m-%d %H:%M:%S")
        echo -e "${BLUE}● PID 文件存在 (修改于 $PID_MODIFIED)${NC}"
    else
        echo -e "${YELLOW}● PID 文件不存在${NC}"
    fi
    
    if [ -f "$LOG_FILE" ]; then
        LOG_SIZE=$(du -sh "$LOG_FILE" | cut -f1)
        LOG_MODIFIED=$(date -r "$LOG_FILE" "+%Y-%m-%d %H:%M:%S")
        echo -e "${BLUE}● 日志文件存在 ($LOG_SIZE, 修改于 $LOG_MODIFIED)${NC}"
    else
        echo -e "${YELLOW}● 日志文件不存在${NC}"
    fi
    
    if [ -f "$OUT_FILE" ]; then
        OUT_SIZE=$(du -sh "$OUT_FILE" | cut -f1)
        OUT_MODIFIED=$(date -r "$OUT_FILE" "+%Y-%m-%d %H:%M:%S")
        echo -e "${BLUE}● 输出文件存在 ($OUT_SIZE, 修改于 $OUT_MODIFIED)${NC}"
    else
        echo -e "${YELLOW}● 输出文件不存在${NC}"
    fi
}

# 主逻辑
parse_args "$@"

case "$ACTION" in
    start)
        start_app
        ;;
    stop)
        stop_app
        ;;
    restart)
        stop_app
        start_app
        ;;
    status)
        show_status
        ;;
    logs)
        show_logs
        ;;
    info)
        show_info
        ;;
    *)
        echo -e "${RED}未知操作: $ACTION${NC}"
        show_help
        ;;
esac    